#include "woptions/woptions.h"

#define BOOST_TEST_MODULE woptions
#include "test.h"

typedef boost::mpl::list<char,wchar_t> char_types;

namespace {
	
template<typename T>
std::basic_string<T> make_str(std::string s)
{
	return std::basic_string<T>(s.begin(),s.end());
}

template<typename T>
std::string make_narrow(std::basic_string<T> s)
{
	return std::string(s.begin(),s.end());
}

}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_constructor, T, char_types )
{
	woptions::basic::Options<T> opts;
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_argument, T, char_types )
{
	woptions::basic::Options<T> opts;
	opts.argument("arg0",woptions::basic::bind_argument_ignore<T>());
	
	{
		std::vector< std::basic_string<T> > args;
		BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
	}
	{
		std::vector< std::basic_string<T> > args(1);
		BOOST_CHECK_NO_THROW( opts.parse(args) );
	}
	{
		std::vector< std::basic_string<T> > args(2);
		BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
	}
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_arguments1, T, char_types )
{
	{
		woptions::basic::Options<T> opts;
		opts.arguments("arg0",0,woptions::basic::bind_argument_ignore<T>());
		
		for(size_t i=0;i<=2;++i)
		{
			std::vector< std::basic_string<T> > args(i);
			BOOST_CHECK_NO_THROW( opts.parse(args) );
		}
	}
	
	{
		woptions::basic::Options<T> opts;
		opts.arguments("arg0",1,woptions::basic::bind_argument_ignore<T>());
		
		{
			std::vector< std::basic_string<T> > args;
			BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
		}
		for(size_t i=1;i<=2;++i)
		{
			std::vector< std::basic_string<T> > args(i);
			BOOST_CHECK_NO_THROW( opts.parse(args) );
		}
	}
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_arguments2, T, char_types )
{
	{
		woptions::basic::Options<T> opts;
		opts.arguments("arg0",0,1,woptions::basic::bind_argument_ignore<T>());
		
		for(size_t i=0;i<=1;++i)
		{
			std::vector< std::basic_string<T> > args(i);
			BOOST_CHECK_NO_THROW( opts.parse(args) );
		}
		{
			std::vector< std::basic_string<T> > args(2);
			BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
		}
	}
	{
		woptions::basic::Options<T> opts;
		opts.arguments("arg0",1,2,woptions::basic::bind_argument_ignore<T>());
		
		{
			std::vector< std::basic_string<T> > args;
			BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
		}
		for(size_t i=1;i<=2;++i)
		{
			std::vector< std::basic_string<T> > args(i);
			BOOST_CHECK_NO_THROW( opts.parse(args) );
		}
		{
			std::vector< std::basic_string<T> > args(3);
			BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
		}
	}
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_option, T, char_types )
{
	{
		woptions::basic::Options<T> opts;
		opts.option(make_str<T>("-o"),"","desc",woptions::basic::bind_option_ignore<T>());
		{
			std::vector< std::basic_string<T> > args(1);
			args[0] = make_str<T>("-o");
			BOOST_CHECK_NO_THROW( opts.parse(args) );
		}
	}
	{
		woptions::basic::Options<T> opts;
		opts.option(make_str<T>("-o"),"(p)","desc",woptions::basic::bind_option_ignore<T>(1));
		{
			std::vector< std::basic_string<T> > args(1);
			args[0] = make_str<T>("-o");
			BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
		}
		{
			std::vector< std::basic_string<T> > args(2);
			args[0] = make_str<T>("-o");
			BOOST_CHECK_NO_THROW( opts.parse(args) );
		}
	}
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_usage, T, char_types )
{
	woptions::basic::Options<T> opts;
	opts.argument("arg0",woptions::basic::bind_argument_ignore<T>());
	opts.argument("arg1",woptions::basic::bind_argument_ignore<T>());
	opts.arguments("arg2",0,woptions::basic::bind_argument_ignore<T>());
	opts.arguments("arg3",1,woptions::basic::bind_argument_ignore<T>());
	opts.arguments("arg4",0,1,woptions::basic::bind_argument_ignore<T>());
	opts.arguments("arg5",1,2,woptions::basic::bind_argument_ignore<T>());
	
	opts.option(make_str<T>("-a"),"","desc",woptions::basic::bind_option_ignore<T>());
	opts.option(make_str<T>("-b"),"(arg)","desc",woptions::basic::bind_option_ignore<T>(1));
	
	opts.group_caption(1,"caption");
	opts.option(make_str<T>("-c"),"","desc",woptions::basic::bind_option_ignore<T>(),1);
	opts.option(make_str<T>("-d"),"(arg)","desc",woptions::basic::bind_option_ignore<T>(1),1);
	
	std::ostringstream grp0;
	opts.usage(make_str<T>("program"),0,grp0);
	
	std::ostringstream grp1;
	opts.usage(make_str<T>("program"),1,grp1);
	
	BOOST_CHECK_EQUAL( grp0.str(),
		"Usage: program [options] (arg1) (arg2)* (arg3)+ [arg4] (arg5)[1-2]\n"
		"Options:\n"
		"-a       desc\n"
		"-b (arg) desc\n" );
	BOOST_CHECK_EQUAL( grp1.str(),
		"caption:\n"
		"-c       desc\n"
		"-d (arg) desc\n" );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_patterns, T, char_types )
{
	std::vector< std::basic_string<T> > arg0;
	std::vector< std::basic_string<T> > arg1;
	std::vector< std::basic_string<T> > arg2;
	std::vector< std::basic_string<T> > arg3;
	

	woptions::basic::Options<T> opts;
	opts.arguments("arg0",1,1,woptions::basic::bind_argument_mem_fun<T>(&arg0,&std::vector< std::basic_string<T> >::push_back));
	opts.arguments("arg1",0,1,woptions::basic::bind_argument_mem_fun<T>(&arg1,&std::vector< std::basic_string<T> >::push_back));
	opts.arguments("arg2",0,  woptions::basic::bind_argument_mem_fun<T>(&arg2,&std::vector< std::basic_string<T> >::push_back));
	opts.arguments("arg3",1,1,woptions::basic::bind_argument_mem_fun<T>(&arg3,&std::vector< std::basic_string<T> >::push_back));
	
	// This should assign to the required arguments first
	{
		arg0.clear(); arg1.clear(); arg2.clear(); arg3.clear();
	
		std::vector< std::basic_string<T> > args;
		args.push_back(make_str<T>("0"));
		args.push_back(make_str<T>("1"));
		BOOST_CHECK_NO_THROW( opts.parse(args) );
		BOOST_REQUIRE_EQUAL( arg0.size(), size_t(1));
		BOOST_REQUIRE_EQUAL( arg1.size(), size_t(0));
		BOOST_REQUIRE_EQUAL( arg2.size(), size_t(0));
		BOOST_REQUIRE_EQUAL( arg3.size(), size_t(1));
		BOOST_CHECK_EQUAL( make_narrow(arg0[0]), "0" );
		BOOST_CHECK_EQUAL( make_narrow(arg3[0]), "1" );
	}
	
	// This should assign to the optional argument first
	{
		arg0.clear(); arg1.clear(); arg2.clear(); arg3.clear();
	
		std::vector< std::basic_string<T> > args;
		args.push_back(make_str<T>("0"));
		args.push_back(make_str<T>("1"));
		args.push_back(make_str<T>("2"));
		BOOST_CHECK_NO_THROW( opts.parse(args) );
		BOOST_REQUIRE_EQUAL( arg0.size(), size_t(1));
		BOOST_REQUIRE_EQUAL( arg1.size(), size_t(1));
		BOOST_REQUIRE_EQUAL( arg2.size(), size_t(0));
		BOOST_REQUIRE_EQUAL( arg3.size(), size_t(1));
		BOOST_CHECK_EQUAL( make_narrow(arg0[0]), "0" );
		BOOST_CHECK_EQUAL( make_narrow(arg1[0]), "1" );
		BOOST_CHECK_EQUAL( make_narrow(arg3[0]), "2" );
	}
	
	// This should assign to the "filler" argument
	{
		arg0.clear(); arg1.clear(); arg2.clear(); arg3.clear();
	
		std::vector< std::basic_string<T> > args;
		args.push_back(make_str<T>("0"));
		args.push_back(make_str<T>("1"));
		args.push_back(make_str<T>("2"));
		args.push_back(make_str<T>("3"));
		args.push_back(make_str<T>("4"));
		BOOST_CHECK_NO_THROW( opts.parse(args) );
		BOOST_REQUIRE_EQUAL( arg0.size(), size_t(1));
		BOOST_REQUIRE_EQUAL( arg1.size(), size_t(1));
		BOOST_REQUIRE_EQUAL( arg2.size(), size_t(2));
		BOOST_REQUIRE_EQUAL( arg3.size(), size_t(1));
		BOOST_CHECK_EQUAL( make_narrow(arg0[0]), "0" );
		BOOST_CHECK_EQUAL( make_narrow(arg1[0]), "1" );
		BOOST_CHECK_EQUAL( make_narrow(arg2[0]), "2" );
		BOOST_CHECK_EQUAL( make_narrow(arg2[1]), "3" );
		BOOST_CHECK_EQUAL( make_narrow(arg3[0]), "4" );
	}
}

BOOST_AUTO_TEST_CASE( test_program_options )
{
	{
		woptions::ProgramOptions opts;
		
		{
			std::vector<std::string> args(0);
			BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
		}
		{
			std::vector<std::string> args(1);
			BOOST_CHECK_NO_THROW( opts.parse(args) );
		}
		{
			std::vector<std::string> args(2);
			BOOST_CHECK_THROW( opts.parse(args), woptions::ArgumentException );
		}
	}
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_single_parameter_options, T, char_types )
{
	int                  ivalue=0;
	std::basic_string<T> svalue;
	
	woptions::basic::Options<T> opts;
	opts.option(make_str<T>("-i"),"(value)","desc",woptions::basic::bind_option_var<T>(ivalue));
	opts.option(make_str<T>("-s"),"(value)","desc",woptions::basic::bind_option_var<T>(svalue));
	
	std::vector< std::basic_string<T> > args(2);
	args[0] = make_str<T>("-i1");
	args[1] = make_str<T>("-s=string");
	
	BOOST_CHECK_NO_THROW( opts.parse(args) );
	BOOST_CHECK_EQUAL( ivalue, 1 );
	BOOST_CHECK_EQUAL( make_narrow(svalue), "string" );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_flags, T, char_types )
{
	bool a = false;
	bool b = false;
	
	woptions::basic::Options<T> opts;
	opts.option(make_str<T>("-a"),"","desc",woptions::basic::bind_option_value<T>(a,true));
	opts.option(make_str<T>("-b"),"","desc",woptions::basic::bind_option_value<T>(b,true));
	
	std::vector< std::basic_string<T> > args(1);
	args[0] = make_str<T>("-ab");
	
	BOOST_CHECK_NO_THROW( opts.parse(args) );
	BOOST_CHECK_EQUAL( a, true );
	BOOST_CHECK_EQUAL( b, true );
}

